#include "OpenGLSupport.h"
#include "OpenGLTexture.h"
#include "OpenGLTexture2D.h"
#include "../Texture.h"
#include "../../HObject.h"
#include "../../Helix.h"
#include "../../Domain/HelixImage.h"

namespace helix
{

	OpenGLTexture2D::OpenGLTexture2D(Image *image,TextureParameters *tParams) : OpenGLTexture(image,tParams)
	{
		if(image == NULL)
		{
			Log().Error("Texture data is NULL");
		}
		else if(tParams->getTextureType() == TextureParameters::TEXTURE_2D)
		{
			inGraphicsMemory=createTexture(image,tParams);
			if(inGraphicsMemory)
				textureTarget=GL_TEXTURE_2D;
		}
		else
		{
			Log().Error("Texture type is not 2D");
			inGraphicsMemory=false;
		}
	}

	OpenGLTexture2D::~OpenGLTexture2D()
	{
		if(inGraphicsMemory)
			glDeleteTextures(1,&textureId);
	}

	bool OpenGLTexture2D::createTexture(Image *image,TextureParameters *tParams)
	{
		if(tParams->getMipMapType() != TextureParameters::AUTO_GENERATE_MIP_MAPS
			&& tParams->getMinFilter() == TextureParameters::TRILINEAR_SAMPLE_FILTERING)
		{
			Log().Error("Cannot use Trilinear filtering without mip-mapping.");
			return false;
		}

		int format=GL_RGB;
		if(image->getChannels() == 4)
			format=GL_RGBA;

		//OpenGL can only create textures of widths and heights equal to a power of 2,
		//so we may need to scale the image data before we can create texture with it
		unsigned char *data=image->getData();
		unsigned int width=image->getWidth();
		unsigned int height=image->getHeight();
		bool scaledImage=false;
		if(!isPowerOf2(width))
			width=findNextPowerOf2(width);
		if(!isPowerOf2(height))
			height=findNextPowerOf2(height);
		if(width!=image->getWidth() || height!=image->getHeight())
		{
			Log().Println("Resizing image data because the dimensions are not powers of 2.");
			scaledImage=true;
			data=new unsigned char[width*height*image->getChannels()];
			int msg=gluScaleImage(
				format,					//RGB or RGBA
				image->getWidth(),		//the original width of the data
				image->getHeight(),		//the original height of the data
				GL_UNSIGNED_BYTE,		//the datatype of the data
				image->getData(),		//the raw data array
				width,					//the width of the new image
				height,					//the height of the new image
				GL_UNSIGNED_BYTE,		//the datatype of the new data
				data);					//a pointer to the new data array to fill

			if(msg!=0)
			{
				Log().Error("Could not scale image data for texture creation.");
				delete[] data;
				return false;
			}
		}		


		glGenTextures(1,&textureId);
		glBindTexture(GL_TEXTURE_2D, textureId);

		// filter to apply when texture is larger than picture
		switch(tParams->getMaxFilter())	
		{
			case TextureParameters::BILINEAR_SAMPLE_FILTERING:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 
				break;
			default:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
				break;
		}

		// filter to apply when texture is smaller than picture
		switch(tParams->getMinFilter())	
		{
			case TextureParameters::TRILINEAR_SAMPLE_FILTERING:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
				break;
			case TextureParameters::BILINEAR_SAMPLE_FILTERING:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
				break;
			default:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
				break;
		}

		//How to generate texture coordinates outside of u = [0.0,1.0]
		switch(tParams->getURepeat()) 
		{
			case TextureParameters::REPEAT:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
				break;
			default:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
				break;
		}

		//How to generate texture coordinates outside of v = [0.0,1.0]
		switch(tParams->getVRepeat()) 
		{
			case TextureParameters::REPEAT:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
				break;
			default:
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
				break;
		}
		
		int textureCreated=1;
		//Do we auto-generate bipmaps, or just generate the texture
		switch(tParams->getMipMapType()) 
		{
			case TextureParameters::AUTO_GENERATE_MIP_MAPS:
				textureCreated=gluBuild2DMipmaps(
					GL_TEXTURE_2D,				//Create a 2d texture
					image->getChannels(),		//number of components
					width,						//width of texture
					height,						//height of texture
					format,						//GL_RGB or GL_RGBA
					GL_UNSIGNED_BYTE,			//data type that each pixel component is comprised of
					data);						//The raw data
				break;
			default:
				glTexImage2D(
					GL_TEXTURE_2D,		//Create a 2d texture
					0,					//We are not doing mip-mapping
					image->getChannels(),	//The internal format of the texture
					width,				//the width of the texture
					height,				//the height of the texture
					0,					//no border
					format,				//GL_RGB or GL_RGBA
					GL_UNSIGNED_BYTE,	//The datatype of the image data,
					data);				//The raw data
				textureCreated=0;
				break;
		}

		//Delete the scaled array if it was created
		if(scaledImage)
			delete [] data;

		if(textureCreated!=0)
		{
			Log().Error("Could not create OpenGL texture.");
			return false;
		}

		return true;
	}

}